--[[--------------------------------------------------------------------
	GridCodeMonitor.lua
----------------------------------------------------------------------]]

local GRIDCODE, GridCode = ...
local L = GridCode.L
local GridCodeRoster = GridCode:GetModule("GridCodeRoster")

local GridCodeMonitor = GridCode:NewModule("GridCodeMonitor")

GridCodeMonitor.modulePrototype = {
	core = GridCodeMonitor,
	Debug = GridCode.Debug,
}

function GridCodeMonitor.modulePrototype:OnInitialize()
	if not self.db then
		self.db = GridCode.db:RegisterNamespace(self.moduleName, { profile = self.defaultDB or { } })
	end

	self.debugFrame = GridCodeMonitor.debugFrame
	self.debugging = self.db.profile.debug

	if type(self.PostInitialize) == "function" then
		self:PostInitialize()
	end
end

function GridCodeMonitor.modulePrototype:OnEnable()
	for monitor, module in GridCodeMonitor:RegisteredMonitorIterator() do
		if module == self.moduleName then
			if self.OnMonitorEnable then
				self:OnMonitorEnable(monitor)
			end
		end
	end

	if type(self.PostEnable) == "function" then
		self:PostEnable()
	end
end

function GridCodeMonitor.modulePrototype:OnDisable()
	for monitor, module in GridCodeMonitor:RegisteredMonitorIterator() do
		if module == self.moduleName then
			if self.OnMonitorDisable then
				self:OnMonitorDisable(monitor)
			end
		end
	end

	if type(self.PostDisable) == "function" then
		self:PostDisable()
	end
end

function GridCodeMonitor.modulePrototype:Reset()
	self.debugging = self.db.profile.debug
	self:Debug("Reset")

	if type(self.PostReset) == "function" then
		self:PostReset()
	end
end

function GridCodeMonitor.modulePrototype:InitializeSettings()
	GridCodeMonitor:Debug("InitializeSettings", self.moduleName)
	if self.extraSettings then
		for name, option in pairs(self.extraSettings) do
			self.settings.args[name] = option
		end
	end
end

function GridCodeMonitor.modulePrototype:RegisterMonitor(monitor, desc, settings, inMainMenu, order)
	GridCodeMonitor:RegisterMonitor(monitor, desc, self.moduleName or true)

	if inMainMenu then
		GridCodeState:Debug("GridCodeState:RegisterState() : inMainMenu = true ", self.moduleName)
	else
		if not self.settings then
			self:InitializeSettings()
		end
		GridCodeMonitor.settings.args[self.moduleName] = self.settings
	end
end

function GridCodeMonitor.modulePrototype:UnregisterMonitor(monitor)
	GridCodeMonitor:UnregisterMonitor(monitor, (self.moduleName or true))
end

function GridCodeMonitor.modulePrototype:SendMonitorGained(...)
	return GridCodeMonitor:SendMonitorGained(...)
end

function GridCodeMonitor.modulePrototype:SendMonitorLost(...)
	return GridCodeMonitor:SendMonitorLost(...)
end

function GridCodeMonitor.modulePrototype:SendMonitorLostAllUnits(...)
	return GridCodeMonitor:SendMonitorLostAllUnits(...)
end

GridCodeMonitor:SetDefaultModulePrototype(GridCodeMonitor.modulePrototype)
GridCodeMonitor:SetDefaultModuleLibraries("AceEvent-3.0")

function GridCode:NewMonitorModule(name, ...)
	return GridCodeMonitor:NewModule(name, ...)
end

------------------------------------------------------------------------

GridCodeMonitor.defaultDB = {
	debug = false,
	range = false,
	colors = {
		PetColorType = "Using Fallback color",
		UNKNOWN_UNIT = { r = 0.5, g = 0.5, b = 0.5, a = 1 },
		UNKNOWN_PET = { r = 0, g = 1, b = 0, a = 1 },
		[L["Beast"]] = { r = 0.93725490196078, g = 0.75686274509804, b = 0.27843137254902, a = 1 },
		[L["Demon"]] = { r = 0.54509803921569, g = 0.25490196078431, b = 0.68627450980392, a = 1 },
		[L["Humanoid"]] = { r = 0.91764705882353, g = 0.67450980392157, b = 0.84705882352941, a = 1 },
		[L["Undead"]] = { r = 0.8, g = 0.2, b = 0, a = 1 },
		[L["Dragonkin"]] = { r = 0.8, g = 0.8, b = 0.8, a = 1 },
		[L["Elemental"]] = { r = 0.8, g = 1, b = 1, a = 1 },
		-- I think this was flying carpets
		[L["Not specified"]] = { r = 0.4, g = 0.4, b = 0.4, a = 1 },
	},
}

------------------------------------------------------------------------

GridCodeMonitor.settings = {
	type = "group",
	name = L["Monitor"],
	desc = L["Options for GridCodeMonitor."],
	args = {
		["color"] = {
			order = -1,
			name = L["Colors"],
			desc = L["Color settings for class and pets."],
			type = "group",
			args = {
				["class"] = {
					order = 100,
					name = L["Class colors"],
					desc = L["Color of player unit classes."],
					type = "group", inline = true,
					args = {
						br = {
							order = 120,
							name = "",
							type = "header",
						},
						reset = {
							order = 130,
							name = L["Reset class colors"],
							desc = L["Reset class colors to defaults."],
							type = "execute", width = "double",
							func = function() GridCodeMonitor:ResetClassColors() end,
						}
					},
				},
				["petcolortype"] = {
					order = 200,
					name = L["Pet coloring"],
					desc = L["Set the coloring strategy of pet units."],
					type = "select", width = "double",
					values = {
						["By Owner Class"] = L["By Owner Class"],
						["By Creature Type"] = L["By Creature Type"],
						["Using Fallback color"] = L["Using Fallback color"],
					},
					get = function()
							return GridCodeMonitor.db.profile.colors.PetColorType
						end,
					set = function(_, v)
							GridCodeMonitor.db.profile.colors.PetColorType = v
							GridCodeMonitor:SendMessage("GridCode_ColorsChanged")
						end,
				},
				["creaturetype"] = {
					order = 300,
					name = L["Creature type colors"],
					desc = L["Color of pet unit creature types."],
					type = "group", inline = true,
					args = {
					},
				},
				["fallback"] = {
					order = 400,
					name = L["Fallback colors"],
					desc = L["Color of unknown units or pets."],
					type = "group", inline = true,
					args = {
						["unit"] = {
							type = "color",
							name = L["Unknown Unit"],
							desc = L["The color of unknown units."],
							order = 410,
							get = function()
									local c = GridCodeMonitor.db.profile.colors.UNKNOWN_UNIT
									return c.r, c.g, c.b, c.a
								end,
							set = function(_, r, g, b, a)
									local c = GridCodeMonitor.db.profile.colors.UNKNOWN_UNIT
									c.r, c.g, c.b, c.a = r, g, b, a
									GridCodeMonitor:SendMessage("GridCode_ColorsChanged")
								end,
							hasAlpha = false,
						},
						["pet"] = {
							type = "color",
							name = L["Unknown Pet"],
							desc = L["The color of unknown pets."],
							order = 420,
							get = function()
									local c = GridCodeMonitor.db.profile.colors.UNKNOWN_PET
									return c.r, c.g, c.b, c.a
								end,
							set = function(_, r, g, b, a)
									local c = GridCodeMonitor.db.profile.colors.UNKNOWN_PET
									c.r, c.g, c.b, c.a = r, g, b, a
									GridCodeMonitor:SendMessage("GridCode_ColorsChanged")
								end,
							hasAlpha = false,
						},
					},
				},
			},
		},
	},
}

------------------------------------------------------------------------

local creatureTypes = { L["Beast"], L["Demon"], L["Humanoid"], L["Undead"], L["Dragonkin"], L["Elemental"], L["Not specified"] }

function GridCodeMonitor:FillColorOptions(settings)
	local classEnglishToLocal = {}
	FillLocalizedClassList(classEnglishToLocal, false)

	local classcolor = {}
	for class, color in pairs(CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS) do
		classcolor[class] = { r = color.r, g = color.g, b = color.b }
	end

	local colors = self.db.profile.colors
	for class in pairs(classcolor) do
		if not colors[class] then
			colors[class] = classcolor[class]
		end
		local classLocal = classEnglishToLocal[class]
		settings.args.class.args[class] = {
			type = "color",
			name = classLocal,
			desc = L["Color for %s."]:format(classLocal),
			get = function()
				local c = colors[class]
				return c.r, c.g, c.b
			end,
			set = function(_, r, g, b)
				local c = colors[class]
				c.r, c.g, c.b = r, g, b
				GridCodeMonitor:SendMessage("GridCode_ColorsChanged")
			end,
		}
	end

	for i = 1, #creatureTypes do
		local class = creatureTypes[i]
		settings.args.creaturetype.args[class] = {
			type = "color",
			name = class,
			desc = L["Color for %s."]:format(class),
			get = function()
				local c = colors[class]
				return c.r, c.g, c.b
			end,
			set = function(_, r, g, b)
				local c = colors[class]
				c.r, c.g, c.b = r, g, b
				GridCodeMonitor:SendMessage("GridCode_ColorsChanged")
			end,
		}
	end
end

function GridCodeMonitor:ResetClassColors()
	local colors = self.db.profile.colors
	for class, class_color in pairs(CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS) do
		local c = colors[class]
		c.r, c.g, c.b = class_color.r, class_color.g, class_color.b
	end
	GridCodeMonitor:SendMessage("GridCode_ColorsChanged")
end

------------------------------------------------------------------------

function GridCodeMonitor:OnInitialize()
	self.super.OnInitialize(self)

	self.registry = {}
	self.registryDescriptions = {}
	self.cache = {}

	if self.options then
		self:FillColorOptions(self.options.args.color)
	end
end

function GridCodeMonitor:OnEnable()
	self.super.OnEnable(self)

	self:RegisterMessage("GridCode_UnitLeft", "RemoveFromCache")
end

function GridCodeMonitor:Reset()
	self.super.Reset(self)

	if self.options then
		self:FillColorOptions(self.options.args.color)
	end

	GridCodeMonitor:SendMessage("GridCode_ColorsChanged")
end

function GridCodeMonitor:OnModuleCreated(module)
	module.super = self.modulePrototype
end

------------------------------------------------------------------------

function GridCodeMonitor:RegisterMonitor(monitor, description, moduleName)
	if not self.registry[monitor] then
		self:Debug("Registered", monitor, "("..description..")", "for", moduleName)
		self.registry[monitor] = (moduleName or true)
		self.registryDescriptions[monitor] = description
		self:SendMessage("GridCode_MonitorRegistered", monitor, description, moduleName)
	else
		-- error if monitor is already registered?
		self:Debug("RegisterMonitor:", monitor, "is already registered.")
	end
end

function GridCodeMonitor:UnregisterMonitor(monitor, moduleName)
	local name

	if self:IsMonitorRegistered(monitor) then
		self:Debug("Unregistered", monitor, "for", moduleName)
		-- need to remove from cache
		for guid in pairs(self.cache) do
			self:SendMonitorLost(guid, monitor)
		end

		-- now we can remove from registry
		self.registry[monitor] = nil
		self.registryDescriptions[monitor] = nil
		self:SendMessage("GridCode_MonitorUnregistered", monitor)
	end
end

function GridCodeMonitor:IsMonitorRegistered(monitor)
	return (self.registry and
		self.registry[monitor] and
		true)
end

function GridCodeMonitor:RegisteredMonitorIterator()
	local monitor
	local gsreg = self.registry
	local gsregdescr = self.registryDescriptions
	return function()
		monitor = next(gsreg, monitor)
		return monitor, gsreg[monitor], gsregdescr[monitor]
	end
end

------------------------------------------------------------------------

function GridCodeMonitor:SendMonitorGained(guid, monitor, priority, range, color, text, value, maxValue, texture, start, duration, stack, texCoords)
	self:Debug("GridCodeMonitor", "SendMonitorGained", guid, monitor, text, value, maxValue)
	if not guid then return end

	local cache = self.cache
	local cached

	if color and not type(color) == "table" then
		self:Debug("color is not a table for", monitor)
	end

	if range and type(range) ~= "number" then
		self:Debug("range is not a number for", monitor)
	end

	if type(texture) == "string" and type(texCoords) ~= "table" then
		self:Debug("texCoords is not a table for", monitor)
		texCoords = nil
	end

	if text == nil then
		text = ""
	end

	-- create cache for unit if needed
	if not cache[guid] then
		cache[guid] = {}
	end

	if not cache[guid][monitor] then
		cache[guid][monitor] = {}
	end

	cached = cache[guid][monitor]

	-- if no changes were made, return rather than triggering an event
	if cached
		and cached.priority == priority
		and cached.range == range
		and cached.color == color
		and cached.text == text
		and cached.value == value
		and cached.maxValue == maxValue
		and cached.texture == texture
		and cached.start == start
		and cached.duration == duration
		and cached.stack == stack
		and cached.texCoords == texCoords
	then
		return
	end

	-- update cache
	cached.priority = priority
	cached.range = range
	cached.color = color
	cached.text = text
	cached.value = value
	cached.maxValue = maxValue
	cached.texture = texture
	cached.start = start
	cached.duration = duration
	cached.stack = stack
	cached.texCoords = texCoords

	self:SendMessage("GridCode_MonitorGained", guid, monitor, priority, range, color, text, value, maxValue, texture, start, duration, stack, texCoords)
end

function GridCodeMonitor:SendMonitorLost(guid, monitor)
	if not guid then return end

	-- if monitor isn't cached, don't send monitor lost event
	if (not self.cache[guid]) or (not self.cache[guid][monitor]) then
		return
	end

	self.cache[guid][monitor] = nil

	self:SendMessage("GridCode_MonitorLost", guid, monitor)
end

function GridCodeMonitor:SendMonitorLostAllUnits(monitor)
	for guid in pairs(self.cache) do
		self:SendMonitorLost(guid, monitor)
	end
end

function GridCodeMonitor:RemoveFromCache(event, guid)
	self.cache[guid] = nil
end

function GridCodeMonitor:GetCachedMonitor(guid, monitor)
	local cache = self.cache
	return (cache[guid] and cache[guid][monitor])
end

function GridCodeMonitor:CachedMonitorIterator(monitor)
	local cache = self.cache
	local guid

	if monitor then
		-- iterator for a specific monitor
		return function()
			guid = next(cache, guid)

			-- we reached the end early?
			if guid == nil then
				return nil
			end

			while cache[guid][monitor] == nil do
				guid = next(cache, guid)

				if guid == nil then
					return nil
				end
			end

			return guid, monitor, cache[guid][monitor]
		end
	else
		-- iterator for all units, all monitors
		return function()
			monitor = next(cache[guid], monitor)

			-- find the next unit with a monitor
			while not monitor do
				guid = next(cache, guid)

				if guid then
					monitor = next(cache[guid], monitor)
				else
					return nil
				end
			end

			return guid, monitor, cache[guid][monitor]
		end
	end
end

------------------------------------------------------------------------

function GridCodeMonitor:UnitColor(guid)
	local unitid = GridCodeRoster:GetUnitidByGUID(guid)

	if not unitid then
		-- bad news if we can't get a unitid
		return
	end

	local colors = self.db.profile.colors

	local owner = GridCodeRoster:GetOwnerUnitidByUnitid(unitid)

	if owner then
		-- if it has an owner, then it's a pet
		local color_type = colors.PetColorType
		if color_type == "By Owner Class" then
			local _, owner_class = UnitClass(owner)
			if owner_class then
				return colors[owner_class]
			end

		elseif color_type == "By Creature Type" then
			local creature_type = UnitCreatureType(unitid)

			-- note that creature_type is nil for Shadowfiends
			if creature_type and colors[creature_type] then
				return colors[creature_type]
			end
		end

		return colors.UNKNOWN_PET
	end

	local _, class = UnitClass(unitid)
	if class then
		return colors[class]
	end

	return colors.UNKNOWN_UNIT
end
